package cn.yitun.chat.yitunchatbasesdk.server.im.dispatcher.impl;

import cn.yitun.chat.yitunactiondispatch.extend.ActionRequest;
import cn.yitun.chat.yitunactiondispatch.extend.ActionResponse;
import cn.yitun.chat.yitunactiondispatch.extend.ArgumentBox;
import cn.yitun.chat.yitunactiondispatch.extend.MethodArgumentResolver;
import cn.yitun.chat.yituncommonlibutil.adapter.BooleanDefault0Adapter;
import cn.yitun.chat.yituncommonlibutil.adapter.DoubleDefault0Adapter;
import cn.yitun.chat.yituncommonlibutil.adapter.IntegerDefault0Adapter;
import cn.yitun.chat.yituncommonlibutil.adapter.LongDefault0Adapter;
import cn.yitun.chat.yituncommonlibutil.factory.NullStringToEmptyAdapterFactory;
import cn.yitun.chat.yituncommonlibutil.lib.YitunJsonPathGenericUtil;
import cn.yitun.chat.yituncommonlibutil.lib.YitunJsonUtil;
import cn.yitun.chat.yituncommonmessage.Head;
import cn.yitun.chat.yitungeneral.annotation.codec.Encryped;
import cn.yitun.chat.yitungeneral.annotation.parameter.JsonDefine;
import cn.yitun.chat.yitungeneral.data.Body;
import cn.yitun.chat.yitungeneral.data.DataNode;
import cn.yitun.chat.yitunspringweb.web.util.YitunAesUtil;
import com.google.gson.*;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;

import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.*;

/**
 * Date 2019-01-12 21:58:45<br>
 * Description
 * @author yitun
 * @since 1.1
 */

public class HandlerMethodArgumentResolver implements MethodArgumentResolver {

	protected final DefaultConversionService conversionService = new DefaultConversionService();
	private String key = "request.message.application/json";

	private static Gson gson;

	private static Gson buildGson() {
		if (gson == null) {
			gson = new GsonBuilder()
					.registerTypeAdapter(int.class, new IntegerDefault0Adapter())
					.registerTypeAdapter(Integer.class, new IntegerDefault0Adapter())
					.registerTypeAdapter(long.class, new LongDefault0Adapter())
					.registerTypeAdapter(Long.class, new LongDefault0Adapter())
					.registerTypeAdapter(double.class, new DoubleDefault0Adapter())
					.registerTypeAdapter(Double.class, new DoubleDefault0Adapter())
					.registerTypeAdapter(boolean.class, new BooleanDefault0Adapter())
					.registerTypeAdapter(Boolean.class, new BooleanDefault0Adapter())
					.registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory())
					.disableHtmlEscaping()
					.serializeNulls().create();
		}
		return gson;
	}

	public HandlerMethodArgumentResolver() {
	
	}

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(JsonDefine.class);
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ActionRequest request, ActionResponse response, ArgumentBox argumentBox) {
		Object object = null;
		JsonDefine define = parameter.getParameterAnnotation(JsonDefine.class);

		// 获取属性定义的名称
		String name = null == define ? null : define.value();

		Type t = parameter.getGenericParameterType();
		Class<?> clazz = parameter.getParameterType();

		// 先从暂存中获取json字符串，如果没有就先将body转为json字符串存入暂存，避免多次转换
		String json = (String) request.getAttribute(key);
		if (null == json) {
			Object data = request.getData();
			boolean isJson = isJson(data);
			// 先判断请求中body是否为json数据
			if (isJson) {
				json = data.toString();
			}
			if (null == json || json.isEmpty()) {
				json = "{}";
			}
			request.addAttribute(key, json);
		}

		Encryped  serializedField = parameter.getMethodAnnotation(Encryped.class);
		//出参是否需要加密
		if (serializedField != null) {
			if (serializedField.decode()) {
				JsonObject newObj = new JsonObject();
				JsonObject topObj = new JsonParser().parse(json).getAsJsonObject();
				newObj.add("head", topObj.get("head"));

				JsonObject  dataObject = topObj.get("body").getAsJsonObject();
				Set<Map.Entry<String, JsonElement>> elementSet =  dataObject.entrySet();
				JsonObject bodyObj = new JsonObject();
				for (Map.Entry<String, JsonElement> element : elementSet) {
					if ( !element.getKey().equals("data")) {
						bodyObj.add(element.getKey(), element.getValue());
					} else {
						try {
							String decodeStr = YitunAesUtil.decrypt(dataObject.get("data").getAsString().replace("\\u0000","").replace("\u0000","")).trim();
							JsonObject dataEle = new JsonParser().parse(decodeStr).getAsJsonObject();
							JsonObject dataObj = new JsonObject();
							Set<Map.Entry<String, JsonElement>> dataElementSet =  dataEle.entrySet();
							for (Map.Entry<String, JsonElement> paramElement : dataElementSet) {
								dataObj.add(paramElement.getKey(), paramElement.getValue());
							}
							bodyObj.add("data", dataEle);
						} catch (Exception e) {
							e.printStackTrace();
							return null;
						}
					}
				}
				newObj.add("body", bodyObj);
				json = buildGson().toJson(newObj);
			}
		}

		boolean isNode = isNode(clazz);
		if (isNode) {
			name = null == name || name.isEmpty() ? getObjectName(clazz) : name;
		}

		Object value = null;

		if (null != name && !name.isEmpty()) {
			value = YitunJsonPathGenericUtil.getGenericObject(json,"$.body." + name, clazz);
		} else {
			value = gson.fromJson(json, clazz);
		}

		if (value instanceof JsonObject) {
			JsonObject jo = (JsonObject) value;
			object = gson.fromJson(jo, t.getClass());
		} else if (value instanceof JsonArray) {
			JsonArray ja = (JsonArray) value;
			object = gson.fromJson(ja, t.getClass());
		} else if (null != value) {
			if (clazz.isInstance(value)) {
				object = value;
			} else {
				if (conversionService.canConvert(value.getClass(), clazz)) {
					object = conversionService.convert(value, clazz);
				}
			}
		}
		if (null == object && isCanInstance(clazz)) {
			try {
				object = clazz.newInstance();
			} catch (InstantiationException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		if (null == object) {
			object = getDefaultValue(clazz);
		}
		return object;
	}

	/**
	 * 
	 * Date 2019-01-06 15:22:00<br>
	 * Description 是否是json数据格式
	 * 
	 * @author yitun<br>
	 * @param data
	 * @return
	 * @since 1.0.0
	 */
	private boolean isJson(Object data) {
		boolean isJson = false;
		if (data instanceof String) {
			isJson = YitunJsonUtil.mayBeJSON(data.toString());
		}
		return isJson;
	}

	/**
	 * Date 2019-01-06 14:53:42<br>
	 * Description 是否可以被实例化
	 * @author yitun
	 * @param classType
	 * @return
	 * @since 1.1
	 */
	private boolean isCanInstance(Class<?> classType) {
		boolean isAbstract = Modifier.isAbstract(classType.getModifiers());
		boolean can = true;
		if (classType.isAnnotation()) {
			can = false;
		} else if (classType.isArray()) {
			can = false;
		} else if (classType.isEnum()) {
			can = false;
		} else if (classType.isInterface()) {
			can = false;
		} else if (isAbstract) {
			can = false;
		}
		return can;
	}

	private boolean isNode(Class<?> clazz) {
		boolean isNode = false;
		boolean isHead = Head.class == clazz || Head.class.isAssignableFrom(clazz);
		boolean isBody = Body.class == clazz || Body.class.isAssignableFrom(clazz);
		boolean isDataNode = DataNode.class == clazz || DataNode.class.isAssignableFrom(clazz);
		isNode = isHead || isBody || isDataNode;
		return isNode;
	}

	private String getObjectName(Class<?> clazz) {
		String name = clazz.getSimpleName();
		if (name.length() > 1) {
			name = name.substring(0, 1).toLowerCase() + name.substring(1);
		} else {
			name = name.toLowerCase();
		}
		return name;
	}

	/**
	 * 
	 * Date 2019-01-06 14:53:28<br>
	 * Description
	 * 
	 * @author yitun<br>
	 * @param clazz
	 * @return
	 * @since 1.0.0
	 */
	@SuppressWarnings("unchecked")
	public <T> T getDefaultValue(Class<T> clazz) {
		Object object = null;
		if (List.class.isAssignableFrom(clazz)) {
			object = new ArrayList<Object>(0);
		} else if (Map.class.isAssignableFrom(clazz)) {
			object = new HashMap<Object, Object>(0);
		} else if (Set.class.isAssignableFrom(clazz)) {
			object = new HashSet<Object>(0);
		} else if (clazz.isArray()) {
			Class<?> componentType = clazz.getComponentType();
			object = Array.newInstance(componentType, 0);
		} else if (clazz == int.class) {
			object = 0;
		} else if (clazz == long.class) {
			object = 0L;
		} else if (clazz == float.class) {
			object = 0.0F;
		} else if (clazz == double.class) {
			object = 0.00D;
		} else if (clazz == byte.class) {
			object = (byte) 0;
		} else if (clazz == char.class) {
			object = '\u0000';
		} else if (clazz == short.class) {
			object = 0;
		} else if (clazz == boolean.class) {
			object = false;
		}
		return ((T) object);
	}
}
